package net.pinnoocle.playservice.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import net.pinnoocle.playcommon.constant.Constant;
import net.pinnoocle.playcommon.constant.wxConstant;
import net.pinnoocle.playcommon.exception.ShopException;
import net.pinnoocle.playcommon.response.ErrorCode;
import net.pinnoocle.playcommon.utils.HelpUtils;
import net.pinnoocle.playcommon.utils.ParseXmlUtil;
import net.pinnoocle.playcommon.utils.WXBizMsgCrypt.AesException;
import net.pinnoocle.playcommon.utils.WXBizMsgCrypt.WXBizMsgCrypt;
import net.pinnoocle.playcommon.utils.WechatUtils;
import net.pinnoocle.playcommon.utils.wxPay.WXPayUtil;
import net.pinnoocle.playdao.entity.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.*;

/**
 * @author ye
 * @Description
 * @create 2021-11-16 15:15
 */
@Service("wxServiceImpl")
@Slf4j
public class WxServiceImpl {

    @Autowired
    VerifyTicketServiceImpl verifyTicketService;
    @Autowired
    ComponentAccessTokenServiceImpl componentAccessTokenService;
    @Autowired
    WxServiceImpl wxService;
    @Autowired
    AuthTokenServiceImpl authTokenService;
    @Autowired
    RegisterWechatServiceImpl registerWechatService;
    @Autowired
    AuthCodeServiceImpl authCodeService;
    @Autowired
    PreCodeServiceImpl preCodeService;
    @Autowired
    UserServiceImpl userService;
    @Autowired
    WechatSubmitServiceImpl wechatSubmitService;
    @Autowired
    ShopWechatSettingServiceImpl shopWechatSettingService;
    @Autowired
    WechatSetNameServiceImpl wechatSetNameService;

    /**
     * 授权事件接收URL
     *
     * @param request
     * @param response
     * @return
     */
    public String acceptOauth(HttpServletRequest request, HttpServletResponse response) {
        String resXml = "";
        InputStream inStream;
        try {
            Map<String, String[]> parameterMap = request.getParameterMap();
            log.info("parameterMap=={}",JSONObject.toJSONString(parameterMap));
            String sMsgSignature = parameterMap.get("msg_signature")[0];
            String timestamp = parameterMap.get("timestamp")[0];
            String nonce = parameterMap.get("nonce")[0];
            inStream = request.getInputStream();
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            WXPayUtil.getLogger().info("授权事件接收URL----start----");
            // 获取微信调用我们notify_url的返回信息
            String resultXml = outSteam.toString("utf-8");
            WXPayUtil.getLogger().info("授权事件接收URL----resultXml----=" + resultXml);
            // 关闭流
            outSteam.close();
            inStream.close();
            // 解密xml
            return this.decryptAuthMsg(sMsgSignature, timestamp, nonce, resultXml);
        } catch (Exception e) {
            WXPayUtil.getLogger().error("授权事件接受异常：", e);
        } finally {
            try {
                // 处理业务完毕
                BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                WXPayUtil.getLogger().error("授权事件接受异常:out：", e);
            }
        }
        return "success";
    }

    /**
     * 获取第三方平台接口调用凭据
     *
     * @return
     */
    public String getComponentAccessToken() {
        ComponentAccessToken accessToken = componentAccessTokenService.getOne(null);
        if (Objects.nonNull(accessToken)) {
            if (!HelpUtils.compareDateToNow(accessToken.getExpireDate())) {
                return accessToken.getComponentAccessToken();
            } else {
                JSONObject componentAccessTokenDo = getComponentAccessTokenDo();
                String componentToken = componentAccessTokenDo.getString("component_access_token");
                Integer expiresIn = componentAccessTokenDo.getInteger("expires_in");
                //token有效期120分钟，实际保存100分钟
                Date expireDate = HelpUtils.afterTime(new Date(), (expiresIn / 60) - 20);
                accessToken.setComponentAccessToken(componentToken);
                accessToken.setExpireDate(expireDate);
                componentAccessTokenService.updateById(accessToken);
                return componentToken;
            }
        } else {
            JSONObject componentAccessTokenDo = getComponentAccessTokenDo();
            String componentToken = componentAccessTokenDo.getString("component_access_token");
            Integer expiresIn = componentAccessTokenDo.getInteger("expires_in");
            //token有效期120分钟，实际保存100分钟
            Date expireDate = HelpUtils.afterTime(new Date(), (expiresIn / 60) - 20);
            ComponentAccessToken token = new ComponentAccessToken();
            token.setComponentAccessToken(componentToken);
            token.setExpireDate(expireDate);
            token.setCreatedTime(new Date());
            componentAccessTokenService.save(token);
            return componentToken;
        }
    }

    /**
     * 获取预授权码
     * @return
     */
    public String getPreCode(){
        PreCode code = preCodeService.getOne(null);
        if(Objects.nonNull(code)){
            if(!HelpUtils.compareDateToNow(code.getExpireTime())){
                return code.getPreAuthCode();
            }else{
                String componentAccessToken = this.getComponentAccessToken();
                JSONObject preCode = WechatUtils.getPreCode(componentAccessToken);
                if(Objects.nonNull(preCode)){
                    String authPreCode = preCode.getString("pre_auth_code");
                    Integer expiresIn = preCode.getInteger("expires_in");
                    int minute = expiresIn/60 - 20;
                    Date date = HelpUtils.afterTime(new Date(), (minute <= 5 ? 10 : minute));
                    code.setPreAuthCode(authPreCode);
                    code.setExpireTime(date);
                    code.setCreatedTime(new Date());
                    preCodeService.updateById(code);
                    return authPreCode;
                }
                return "";
            }
        }else{
            String componentAccessToken = this.getComponentAccessToken();
            JSONObject preCode = WechatUtils.getPreCode(componentAccessToken);
            if(Objects.nonNull(preCode)){
                String authPreCode = preCode.getString("pre_auth_code");
                Integer expiresIn = preCode.getInteger("expires_in");
                int minute = expiresIn/60 - 20;
                Date date = HelpUtils.afterTime(new Date(), (minute <= 5 ? 10 : minute));
                PreCode preCodeObject = new PreCode();
                preCodeObject.setPreAuthCode(authPreCode);
                preCodeObject.setExpireTime(date);
                preCodeObject.setCreatedTime(new Date());
                preCodeService.save(preCodeObject);
                return authPreCode;
            }
            return "";
        }
    }

    /**
     * 获取accessToken
     * @return
     */
    private JSONObject getComponentAccessTokenDo(){
        VerifyTicket verifyTicket = verifyTicketService.getOne(null);
        if (Objects.isNull(verifyTicket)) {
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR, "票据获取失败");
        }
        JSONObject componentAccessToken = WechatUtils.getComponentAccessToken(verifyTicket.getVerifyTicket());
        if (componentAccessToken.containsKey("errcode") && componentAccessToken.getInteger("errcode") > 0) {
            log.info("获取第三方平台接口调用凭据失败===" + componentAccessToken.getString("errmsg"));
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR, "获取第三方平台接口调用凭据失败");
        } else {
            return componentAccessToken;
        }
    }

    /**
     * 启动推送服务
     * @return
     */
    public String startPushTicket(){
       JSONObject componentAccessToken = WechatUtils.startPushTicket();
        if (componentAccessToken.containsKey("errcode") && componentAccessToken.getInteger("errcode") > 0) {
            log.info("启动推送服务失败===" + componentAccessToken.getString("errmsg"));
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR, "启动推送服务失败");
        }
        return "success";
    }

    /**
     * 消息与事件接收URL
     * @param request
     * @param response
     * @param appId
     * @return
     */
    public String acceptMsg(HttpServletRequest request, HttpServletResponse response,String appId){
        String resXml = "";
        InputStream inStream;
        try {
            Map<String, String[]> parameterMap = request.getParameterMap();
            log.info("acceptMsgMap=={}",JSONObject.toJSONString(parameterMap));
            String sMsgSignature = parameterMap.get("msg_signature")[0];
            String timestamp = parameterMap.get("timestamp")[0];
            String nonce = parameterMap.get("nonce")[0];
            inStream = request.getInputStream();
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            WXPayUtil.getLogger().info("消息与事件接收URL----start----");
            // 获取微信调用我们notify_url的返回信息
            String resultXml = outSteam.toString("utf-8");
            log.info("acceptMsg-resultXml==="+resultXml);
            // 关闭流
            outSteam.close();
            inStream.close();
            // 解密xml
            String decryXml = this.decryptXml(sMsgSignature, timestamp, nonce, resultXml);
            log.info("acceptMsg-decryXml==={}",decryXml);
            Map<String, String> decryMap = WXPayUtil.xmlToMap(decryXml);
            log.info("acceptMsg-decryMap==={}",decryMap);
            String msgType = decryMap.getOrDefault("MsgType", "");
            String eventType = decryMap.getOrDefault("Event", "");
            if(StringUtils.isNotBlank(msgType) && msgType.equals("event")){
                //代码审核通知
                if(eventType.equals("weapp_audit_success") || eventType.equals("weapp_audit_fail")
                        || eventType.equals("weapp_audit_delay")){
                    WechatSubmit submit = wechatSubmitService.getOne(new QueryWrapper<WechatSubmit>()
                            .lambda()
                            .eq(WechatSubmit::getAppid,appId)
                            .eq(WechatSubmit::getResult,0));
                    //代码审核通过
                    if(eventType.equals("weapp_audit_success")){
                        String succTime = decryMap.getOrDefault("SuccTime", "");
                        submit.setResult(1);
                        submit.setCheckTime(HelpUtils.getDateByTime(succTime));
                    }else if(eventType.equals("weapp_audit_fail")){ // 代码审核不通过
                        String failTime = decryMap.getOrDefault("FailTime", "");
                        String screenShot = decryMap.getOrDefault("ScreenShot", "");
                        String reason = decryMap.getOrDefault("Reason", "");
                        submit.setResult(2);
                        submit.setReason(reason);
                        submit.setCheckTime(HelpUtils.getDateByTime(failTime));
                        submit.setScreenShot(screenShot);
                    }else if(eventType.equals("weapp_audit_delay")){
                        String delayTime = decryMap.getOrDefault("DelayTime", "");
                        String reason = decryMap.getOrDefault("Reason", "");
                        submit.setResult(3);
                        submit.setReason(reason);
                        submit.setCheckTime(HelpUtils.getDateByTime(delayTime));
                    }
                    wechatSubmitService.updateById(submit);
                }
                //小程序昵称审核通知
                if(eventType.equals("wxa_nickname_audit")){
                    String nickname = decryMap.getOrDefault("nickname", "");
                    String ret = decryMap.getOrDefault("ret", "2");
                    int checkResult = Integer.parseInt(ret);
                    String reason = decryMap.getOrDefault("reason", "");
                    WechatSetName setOne = wechatSetNameService.getOne(new QueryWrapper<WechatSetName>().lambda()
                            .eq(WechatSetName::getNickName, nickname)
                            .eq(WechatSetName::getResult, 0), false);
                    setOne.setResult(checkResult == 3 ? 1 : 2);
                    setOne.setReason(reason);
                    wechatSetNameService.updateById(setOne);
                    String shopId = setOne.getShopId();
                    ShopWechatSetting shopWechatSetting = shopWechatSettingService.getOne(new QueryWrapper<ShopWechatSetting>()
                            .lambda()
                            .eq(ShopWechatSetting::getShopId, shopId));
                    shopWechatSetting.setSetNickName(checkResult == 3 ? 1 : 3);
                    shopWechatSettingService.updateById(shopWechatSetting);
                }
            }

        } catch (Exception e) {
            WXPayUtil.getLogger().error("消息与事件接收URL：", e);
        } finally {
            try {
                // 处理业务完毕
                BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                WXPayUtil.getLogger().error("消息与事件接收URL:out：", e);
            }
        }
        return "success";
    }

    /**
     * 微信加密内容解密
     * @param msgSignature
     * @param timestamp
     * @param nonce
     * @param fromXML
     * @return
     */
    public String decryptAuthMsg(String msgSignature, String timestamp, String nonce, String fromXML) throws Exception {
        String resultXml = decryptXml(msgSignature, timestamp, nonce, fromXML);
        log.info("decryptMsg-resultXml===="+resultXml);
        Map<String, Object> resultMap = ParseXmlUtil.xml2map(resultXml);
        log.info("decryptMsg-resultMap===={}",resultMap);
        Object xmlObject = resultMap.getOrDefault("xml",null);
        if(Objects.isNull(xmlObject)){
            return "success";
        }
        JSONObject xmlToJsonObject = JSON.parseObject(JSONObject.toJSONString(xmlObject));
        String infoType = xmlToJsonObject.getString("InfoType");
        //推送令牌
        if(Objects.equals(infoType, "component_verify_ticket")){
            String ComponentVerifyTicket = xmlToJsonObject.getString("ComponentVerifyTicket");
            VerifyTicket ticket = verifyTicketService.getOne(null);
            if(Objects.isNull(ticket)){
                VerifyTicket verifyTicket = new VerifyTicket();
                Date expireDate = HelpUtils.afterTime(new Date(), 720);
                verifyTicket.setVerifyTicket(ComponentVerifyTicket);
                verifyTicket.setExpireTime(expireDate);
                verifyTicket.setCreatedTime(new Date());
                verifyTicketService.save(verifyTicket);
            }else{
                Date expireDate = HelpUtils.afterTime(new Date(), 720);
                ticket.setVerifyTicket(ComponentVerifyTicket);
                ticket.setExpireTime(expireDate);
                ticket.setCreatedTime(new Date());
                verifyTicketService.updateById(ticket);
            }
        }else if(Objects.equals(infoType, "notify_third_fasteregister")){ // 快速注册小程序
            String appid = xmlToJsonObject.getString("appid");
            Integer status = xmlToJsonObject.getInteger("status");
            String msg = xmlToJsonObject.getString("msg");
            JSONObject companyInfo = JSON.parseObject(xmlToJsonObject.getString("info"));
            String code = companyInfo.getString("code");
            String personaWechat = companyInfo.getString("legal_persona_wechat");
            //更新申请结果
            RegisterWechat registerWechat = registerWechatService.getOne(new QueryWrapper<RegisterWechat>()
                    .lambda()
                    .eq(RegisterWechat::getCompanyCode, code)
                    .eq(RegisterWechat::getPersonaWechat, personaWechat),false);
            if(Objects.nonNull(registerWechat)){
                registerWechat.setAppid(appid);
                registerWechat.setErrcode(status);
                registerWechat.setErrmsg(msg);
            }
            //更新店铺小程序信息
            String shopId = registerWechat.getShopId();
            ShopWechatSetting shopWechatSetting = shopWechatSettingService.getOne(new QueryWrapper<ShopWechatSetting>()
                    .lambda()
                    .eq(ShopWechatSetting::getShopId, shopId));
            if(Objects.nonNull(shopWechatSetting)){
                shopWechatSetting.setAppId(appid);
                shopWechatSettingService.updateById(shopWechatSetting);
            }
        }
        return "success";
    }

    /**
     * 解密xml
     * @param msgSignature
     * @param timestamp
     * @param nonce
     * @param fromXML
     * @return
     */
    public String decryptXml(String msgSignature, String timestamp, String nonce, String fromXML) throws AesException {
        String token = wxConstant.sToken;
        String encodingAesKey = wxConstant.sEncodingAESKey;
        String appId = wxConstant.sAppid;
        WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt(token, encodingAesKey, appId);
        return wxBizMsgCrypt.decryptMsg(msgSignature, timestamp, nonce, fromXML);
    }

    /**
     * 获取授权二维码
     * @return
     */
    public String getAuthQrCode(){
        String redirectUrl = Constant.AUTH_REDIRECT_URI;
        String componentAppid = wxConstant.sAppid;
        String preAuthCode = this.getPreCode();
        if(StringUtils.isBlank(preAuthCode)){
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR,"获取授权二维码失败");
        }
        return MessageFormat.format(wxConstant.AUTH_QR_CODE, new String[]{componentAppid, preAuthCode,redirectUrl});
    }

    /**
     * 获取/刷新接口调用令牌
     * @param shopId
     * @return
     */
    public String getAuthorizerToken(String shopId){
        ShopWechatSetting wechatSetting = shopWechatSettingService.getOne(new QueryWrapper<ShopWechatSetting>()
                .lambda()
                .eq(ShopWechatSetting::getShopId, shopId));
        if(Objects.isNull(wechatSetting)){
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR,"小程序信息未配置");
        }
        String appid = wechatSetting.getAppId();
        if(StringUtils.isBlank(appid)){
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR,"请配置小程序的appId");
        }
        //获取授权token
        AuthToken authToken = authTokenService.getOne(new QueryWrapper<AuthToken>().lambda()
                .eq(AuthToken::getAuthorizerAppid, appid));
        if(Objects.isNull(authToken)){
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR,"授权信息获取失败，请联系管理员");
        }
        if(!HelpUtils.compareDateToNow(authToken.getExpireDate())){
            return authToken.getAuthorizerAccessToken();
        }else{
            String componentAccessToken = this.getComponentAccessToken();
            JSONObject authorizerToken = WechatUtils.getAuthorizerToken(componentAccessToken, authToken.getAuthorizerAppid(),
                    authToken.getAuthorizerRefreshToken());
            if(Objects.nonNull(authorizerToken)){
                String accessToken = authorizerToken.getString("authorizer_access_token");
                int expiresIn = authorizerToken.getInteger("expires_in");
                authToken.setAuthorizerAccessToken(accessToken);
                Date expiresDate = HelpUtils.afterTimeBySecond(expiresIn);
                authToken.setExpireDate(expiresDate);
                authTokenService.updateById(authToken);
                return accessToken;
            }
            throw new ShopException(ErrorCode.PARAMS_GET_ERROR,"授权信息获取失败，请联系管理员");
        }
    }


    /**
     * 保存AuthCode换取的授权信息
     * @param authInfoByAuthCode
     * @return
     */
    public void saveAccessToken(JSONObject authInfoByAuthCode){
        if(Objects.nonNull(authInfoByAuthCode)){
            JSONObject info = authInfoByAuthCode.getJSONObject("authorization_info");
            String authorizerAppid = info.getString("authorizer_appid");
            String authorizerAccessToken = info.getString("authorizer_access_token");
            int accessTokenExpireIn = info.getInteger("expires_in");
            Date accessTokenExpireDate = HelpUtils.afterTimeBySecond(accessTokenExpireIn);
            String authorizerRefreshToken = info.getString("authorizer_refresh_token");
            AuthToken authToken = authTokenService.getOne(new QueryWrapper<AuthToken>()
                    .lambda()
                    .eq(AuthToken::getAuthorizerAppid, authorizerAppid), false);
            if(Objects.isNull(authToken)){
                AuthToken authTokenObject = new AuthToken();
                authTokenObject.setAuthorizerAppid(authorizerAppid);
                authTokenObject.setAuthorizerAccessToken(authorizerAccessToken);
                authTokenObject.setAuthorizerRefreshToken(authorizerRefreshToken);
                authTokenObject.setExpireDate(accessTokenExpireDate);
                authTokenObject.setCreatedTime(new Date());
                authTokenService.save(authTokenObject);
            }else{
                authToken.setAuthorizerAccessToken(authorizerAccessToken);
                authToken.setAuthorizerRefreshToken(authorizerRefreshToken);
                authToken.setExpireDate(accessTokenExpireDate);
                authToken.setCreatedTime(new Date());
                authTokenService.updateById(authToken);
            }
        }
    }

    /**
     * 授权回调地址，获取授权码
     * @param request
     * @param response
     * @return
     */
    public String authRedirectUri(HttpServletRequest request, HttpServletResponse response){
        Map<String, String[]> parameterMap = request.getParameterMap();
        log.info("authRedirectUri-parameterMap=={}",JSONObject.toJSONString(parameterMap));
        String code = parameterMap.get("auth_code")[0];
        //String expiresIn = parameterMap.get("expires_in")[0];
        //Date authCodeExpireDate = HelpUtils.afterTimeBySecond(Integer.parseInt(expiresIn));
        //授权码
        if(StringUtils.isNotBlank(code)){
            /*AuthCode codeRecord = authCodeService.getOne(null);
            if(Objects.isNull(codeRecord)){
                AuthCode authCode = new AuthCode();
                authCode.setAuthCode(code);
                authCode.setExpireTime(authCodeExpireDate);
                authCode.setCreatedTime(new Date());
                authCodeService.save(authCode);
            }else{
                codeRecord.setAuthCode(code);
                codeRecord.setExpireTime(authCodeExpireDate);
                codeRecord.setCreatedTime(new Date());
                authCodeService.updateById(codeRecord);
            }*/
            //获取到auth_code后取换取授权信息
            String componentAccessToken = this.getComponentAccessToken();
            JSONObject authInfoByAuthCode = WechatUtils.getAuthInfoByAuthCode(code, componentAccessToken);
            saveAccessToken(authInfoByAuthCode);
        }
        return "success";
    }
}
